#!/usr/bin/python
# -*- coding: utf8 -*-

from pyparsing import Literal

from expresion import Expresion, Tipo, Exp, TipoExp, TypeException, makeSintaxis

from lenguaje import Extension


class TipoAB(Tipo):
    
    def __init__(self, tok):
        """
        El tipo para arboles binarios esta compuesto,
        justamente por el tipo de arbol que representara.
        """
        self.tipo = tok[0]
        
    def __eq__(self, other):
        """
        Un tipo es igual a un tipo de arbol binario, si en principio es
        un arbol binario, y ademas los tipos de los arboles son los mismos.
        """
        return self.__class__ == other.__class__ and \
               self.tipo == other.tipo 
    
    @classmethod
    def sintaxis(cls):
        Arbol = makeSintaxis('AB(',TipoExp,')')
        return Arbol
    
    def __str__(self):
        return 'AB(%s)' % self.tipo
        
    
class Bin(Expresion):
    
    def __init__(self, tok):
        """
        Un constructor de arbol se compone
        de un nodo, un arbol izquierdo y uno derecho.
        """
        izq, raiz, der = tok
        self.izq = izq
        self.raiz = raiz
        self.der = der
    
    def reducir(self):
        """
        El resultado de reducir un arbol binario es otro,
        con tanto su nodo, como sus subarboles estan reducidos.
        """
        i = self.izq.reducir()
        r = self.raiz.reducir()
        d = self.der.reducir()
        return Bin([i, r, d])
        
    def tipar(self, namespace):
        """
        gamma | > R: alpha, gamma | > I : AB(alpha), gamma | > D :AB(alpha)
        -------------------------------------------------------------------
        gamma | > bin(I,R,D) : AB(alpha)
        """
        ti = self.izq.tipar(namespace)
        tr = self.raiz.tipar(namespace)
        td = self.der.tipar(namespace)
        if isinstance(ti, TipoAB) and isinstance(td, TipoAB) and ti == td and ti.tipo == tr:
            return TipoAB([tr])
        else:
            raise TypeException(('Tipo subarbol izquierdo: %s \n'+ \
                                'Tipo raiz: %s \n'+ \
                                'Tipo subarbol derecho: %s \n'+ \
                                'El tipo de los subarboles debe unificar con el tipo ArbolBinario y entre si mismos. \n' + \
                                'El tipo de la raiz debe coincidir con el tipo asociado al los subarboles que lo componen.') % (ti, tr, td))
        
    
    def sustituir(self, var, expresion):
        """
        bin(I,R,D) {X <- T} --> bin(I {X <- T}, R {X <- T}, D {X <-T})
        """
        i = self.izq.sustituir(var, expresion)
        r = self.raiz.sustituir(var, expresion)
        d = self.der.sustituir(var, expresion)
        return Bin([i, r, d])
    
    @classmethod
    def sintaxis(cls):
        Arbol = makeSintaxis('bin(', Exp ,',', Exp , ',', Exp,')')
        return Arbol    
    
    def __str__(self):
        return "bin(%s, %s, %s)" % (self.izq, self.raiz, self.der)
        
    
class Nil(Expresion):
    
    def __init__(self, toks):
        """
        El arbol nil esta compuesto por el tipo de arbol que es.
        """
        self.tipo = toks[0]
        
    def reducir(self):
        """
        Reducir nil retorna si mismo.
        """
        return self
    
    def tipar(self, namespace):
        """
        vacio | > nil<alpha> : AB(alpha)
        """
        return TipoAB([self.tipo])
    
    def sustituir(self, var, expresion):
        """
        nil<alpha> {X <- T} --> nil<alpha>
        """
        return self
    
    @classmethod
    def sintaxis(cls):
        Nil = makeSintaxis('nil(',  TipoExp, ')')
        return Nil
    
    def __str__(self):
        return "nil(%s)" % self.tipo
        
class CaseAB(Expresion):
    
    def __init__(self, toks):
        """
        Un case de arbol esta compuesto por:
        - El tipo de arbol del case.
        - El arbol a observar.
        - El resultado por matching con el arbol nil.
        - El resultado por mathing con el arbol bin(I,R,D).
        """
        tipo, term, resNil, resBin = toks
        self.tipo = tipo
        self.term = term
        self.resNil = resNil
        self.resBin = resBin
        
    def reducir(self):
        """
        En principio se reduce el arbol a analizar, luego:
        - Si es nil se reduce el resultado por matching con el arbol nil.
        - Si no:
            - caseAB(alpha) bin(I,R,D); of nil -> W; bin(M,N,O) -> Z --> Z {M <- I, N <- R, O <- D}
            - Luego se reduce Z {M <- I, N <- R, O <- D}.
        """
        termR = self.term.reducir()
        if isinstance(termR, Nil):
            return self.resNil.reducir()
        else:
            sust1 = self.resBin.sustituir('M', termR.izq)
            sust2 = sust1.sustituir('N', termR.raiz)
            sust3 = sust2.sustituir('O', termR.der)
            return sust3.reducir()
        
    def tipar(self, namespace):
        """
        gamma | > A : AB(alpha), gamma | > B : beta, gamma,{M:AB(alpha),N:alpha,O:AB(alpha)} | > C : beta
        -------------------------------------------------------------------------------------------------
        gamma | > caseAB(alpha) A; of nil -> B; bin(M,N,O) -> C : beta
        """
        tt = self.term.tipar(namespace)
        trn = self.resNil.tipar(namespace)
        name2 = dict(namespace)
        name2['M'] = TipoAB([self.tipo])
        name2['N'] = self.tipo
        name2['O'] = TipoAB([self.tipo])
        trb = self.resBin.tipar(name2)
        if tt == TipoAB([self.tipo]) and trn == trb:
            return trb
        else:
            raise TypeException(('Tipo Termino: %s \n'+ \
                                'Tipo Case Nil: %s \n'+ \
                                'Tipo Case Bin: %s \n'+ \
                                'El tipo de los resultados por matching Nil o Bin debe ser el mismo. \n' + \
                                'El tipo del Termino debe ser el mismo que el asociado a la clausula Case.') % (tt, trn, trb))
    
    def sustituir(self, var, expresion):
        """
        (caseAB(alpha) A; of nil -> B; bin(M,N,O) -> C) {X <- T} --> caseAB(alpha) A'; of nil -> B'; bin(M,N,O) -> C'
        donde:
        - A' = A {X <- T}
        - B' = B {X <- T}
        - C' = C {X <- T} si (X != M && X != N && X != O)
        - C' = C si (X == M || X == N || X == O)
        """
        sterm = self.term.sustituir(var, expresion)
        sresNil = self.resNil.sustituir(var, expresion)
        if var != 'M' and var != 'N' and var != 'O': 
            sresBin = self.resBin.sustituir(var, expresion)
        else:
            sresBin = self.resBin
        return CaseAB([self.tipo, sterm, sresNil, sresBin])

    @classmethod
    def sintaxis(cls):
        Case = makeSintaxis('caseAB(', TipoExp,') ', Exp, '; of nil -> ', Exp ,'; bin(M,N,O) -> ', Exp)
        return Case    
    
    def __str__(self):
        return 'caseAB(%s) %s; of nil -> %s ; bin(M,N,O) -> %s' % (self.tipo, self.term, self.resNil, self.resBin)
        
extensionArbol = Extension("Arboles", expresiones=[Bin, Nil, CaseAB], tipos=[TipoAB])
